package com.zl.common.aop;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import com.zl.common.utils.JsonUtil;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;

/**  
  * 统一操作日志记录中心
 */ 
@Aspect
@Component
public class GlobalLogFilter {
    private static Logger LOGGER = LoggerFactory.getLogger(GlobalLogFilter.class);
    private static final ThreadLocal<Long> timeTreadLocal = new ThreadLocal<>();
    
    private static final String LogIgnorePath = "@annotation(com.zl.common.aop.LogIgnore)";
    private static final String LogSimpleResultPath = "@annotation(com.zl.common.aop.LogSimpleResult)";
    private static final String ControllerPath = "execution(* *..*.*Controller.*(..))";
    
    
    //请求Controller输出日志
    @Pointcut(ControllerPath+"&&!"+LogIgnorePath)
    public void controllerAspect() {
    }
    //请求Controller不输出返回内容
    @Pointcut(LogSimpleResultPath)
    public void resultSimpleAspect() {
    }

    //请求日志记录
    @Before("controllerAspect()")
    public void before(JoinPoint joinPoint) {
        timeTreadLocal.set(System.currentTimeMillis());
        // 接收请求，记录完整请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取请求的request
        HttpServletRequest request = attributes.getRequest();
        String methodName = getMethodName(joinPoint);
        
        String keyValue = getReqData(joinPoint,request);
        LOGGER.info("{}-req:请求uri={},请求Method={},ContentType={}",methodName,request.getRequestURI(),request.getMethod(),request.getContentType());
        LOGGER.info("{}-req:请求参数={}",methodName, keyValue);
    }
    //后置返回
    @AfterReturning(pointcut = "controllerAspect() && !resultSimpleAspect()",returning = "result")    
    public void doAfterReturning(JoinPoint joinPoint,Object result) {
        long startTime = timeTreadLocal.get();
        double callTime = (System.currentTimeMillis() - startTime) / 1000.0;
        String methodName = getMethodName(joinPoint);
        LOGGER.info("{}-res:耗时{}s,成功,返回数据={}",methodName, callTime, JsonUtil.convertToJson2(result));
        timeTreadLocal.remove();
    }
    //后置返回(不输出返回内容)
    @AfterReturning(pointcut = "controllerAspect() && resultSimpleAspect()",returning = "result")
    public void doAfterReturningSimple(JoinPoint joinPoint,Object result) {
        long startTime = timeTreadLocal.get();
        double callTime = (System.currentTimeMillis() - startTime) / 1000.0;
        String methodName = getMethodName(joinPoint);
        LOGGER.info("{}-res:耗时{}s,成功,返回数据=该方法输出内容省略...",methodName, callTime);
        timeTreadLocal.remove();
    }  
    //后置异常返回
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")    
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        long startTime = timeTreadLocal.get();
        double callTime = (System.currentTimeMillis() - startTime) / 1000.0;
        String methodName = getMethodName(joinPoint);
        LOGGER.info("{}-res:耗时{}s,失败,异常={}",methodName, callTime,e.getMessage());
        timeTreadLocal.remove();
    }
    
    /** 获取方法名称*/
    private String getMethodName(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //获取被拦截的方法
        Method method = methodSignature.getMethod();
        String className = method.getDeclaringClass().getCanonicalName();
        String methodName = className.substring(className.lastIndexOf(".")+1) + "." + method.getName();
        return methodName;
    }
    /**所有请求参数*/
    private String getReqData(JoinPoint joinPoint, HttpServletRequest request){
        try {
            Object[] obj = joinPoint.getArgs();
            List<Object> req = new ArrayList<>();
            for(Object x: obj){
                String clz = x.getClass().toString();
                LOGGER.info("获取参数>>{}", x.getClass());
                //BindingResult || ServletRequest || ServletResponse || MultipartFile)
                if(!clz.contains("org.apache.catalina") 
                        && !clz.contains("javax.servlet") && !clz.contains("org.springframework")){
                    req.add(x);
                }
            }
            if(req.size() == 0){
                if(StringUtils.isBlank(request.getContentType()) || !request.getContentType().contains(ContentType.JSON.getValue())){
                    Map<String, String> reqParam = ServletUtil.getParamMap(request);
                    return JsonUtil.convertToJson2(reqParam);
                }
            }
            return JsonUtil.convertToJson2(req);
        } catch (Exception ex) {
            LOGGER.error("获取参数异常>>{}", ex);
        }
        return null;
    }
    
    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
            || o instanceof BindingResult;
    }
}    